// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/tools/flip_server/output_ordering.h"

#include <utility>

#include "net/tools/flip_server/flip_config.h"
#include "net/tools/flip_server/sm_connection.h"

namespace net {

OutputOrdering::PriorityMapPointer::PriorityMapPointer()
    : ring(NULL)
    , alarm_enabled(false)
{
}

OutputOrdering::PriorityMapPointer::PriorityMapPointer(
    const PriorityMapPointer& other)
    = default;

OutputOrdering::PriorityMapPointer::~PriorityMapPointer() { }

// static
double OutputOrdering::server_think_time_in_s_ = 0.0;

OutputOrdering::OutputOrdering(SMConnectionInterface* connection)
    : first_data_senders_threshold_(kInitialDataSendersThreshold)
    , connection_(connection)
{
    if (connection)
        epoll_server_ = connection->epoll_server();
}

OutputOrdering::~OutputOrdering() { Reset(); }

void OutputOrdering::Reset()
{
    while (!stream_ids_.empty()) {
        StreamIdToPriorityMap::iterator sitpmi = stream_ids_.begin();
        PriorityMapPointer& pmp = sitpmi->second;
        if (pmp.alarm_enabled) {
            epoll_server_->UnregisterAlarm(pmp.alarm_token);
        }
        stream_ids_.erase(sitpmi);
    }
    priority_map_.clear();
    first_data_senders_.clear();
}

bool OutputOrdering::ExistsInPriorityMaps(uint32_t stream_id) const
{
    StreamIdToPriorityMap::const_iterator sitpmi = stream_ids_.find(stream_id);
    return sitpmi != stream_ids_.end();
}

OutputOrdering::BeginOutputtingAlarm::BeginOutputtingAlarm(
    OutputOrdering* oo,
    OutputOrdering::PriorityMapPointer* pmp,
    const MemCacheIter& mci)
    : output_ordering_(oo)
    , pmp_(pmp)
    , mci_(mci)
    , epoll_server_(NULL)
{
}

OutputOrdering::BeginOutputtingAlarm::~BeginOutputtingAlarm()
{
    if (epoll_server_ && pmp_->alarm_enabled)
        epoll_server_->UnregisterAlarm(pmp_->alarm_token);
}

int64_t OutputOrdering::BeginOutputtingAlarm::OnAlarm()
{
    OnUnregistration();
    output_ordering_->MoveToActive(pmp_, mci_);
    VLOG(2) << "ON ALARM! Should now start to output...";
    delete this;
    return 0;
}

void OutputOrdering::BeginOutputtingAlarm::OnRegistration(
    const EpollServer::AlarmRegToken& tok,
    EpollServer* eps)
{
    epoll_server_ = eps;
    pmp_->alarm_token = tok;
    pmp_->alarm_enabled = true;
}

void OutputOrdering::BeginOutputtingAlarm::OnUnregistration()
{
    pmp_->alarm_enabled = false;
    delete this;
}

void OutputOrdering::BeginOutputtingAlarm::OnShutdown(EpollServer* eps)
{
    OnUnregistration();
}

void OutputOrdering::MoveToActive(PriorityMapPointer* pmp, MemCacheIter mci)
{
    VLOG(2) << "Moving to active!";
    first_data_senders_.push_back(mci);
    pmp->ring = &first_data_senders_;
    pmp->it = first_data_senders_.end();
    --pmp->it;
    connection_->ReadyToSend();
}

void OutputOrdering::AddToOutputOrder(const MemCacheIter& mci)
{
    if (ExistsInPriorityMaps(mci.stream_id))
        LOG(ERROR) << "OOps, already was inserted here?!";

    double think_time_in_s = server_think_time_in_s_;
    std::string x_server_latency = mci.file_data->headers()->GetHeader("X-Server-Latency").as_string();
    if (!x_server_latency.empty()) {
        char* endp;
        double tmp_think_time_in_s = strtod(x_server_latency.c_str(), &endp);
        if (endp != x_server_latency.c_str() + x_server_latency.size()) {
            LOG(ERROR) << "Unable to understand X-Server-Latency of: "
                       << x_server_latency
                       << " for resource: " << mci.file_data->filename().c_str();
        } else {
            think_time_in_s = tmp_think_time_in_s;
        }
    }
    StreamIdToPriorityMap::iterator sitpmi;
    sitpmi = stream_ids_.insert(std::pair<uint32_t, PriorityMapPointer>(
                                    mci.stream_id, PriorityMapPointer()))
                 .first;
    PriorityMapPointer& pmp = sitpmi->second;

    BeginOutputtingAlarm* boa = new BeginOutputtingAlarm(this, &pmp, mci);
    VLOG(1) << "Server think time: " << think_time_in_s;
    epoll_server_->RegisterAlarmApproximateDelta(think_time_in_s * 1000000, boa);
}

void OutputOrdering::SpliceToPriorityRing(PriorityRing::iterator pri)
{
    MemCacheIter& mci = *pri;
    PriorityMap::iterator pmi = priority_map_.find(mci.priority);
    if (pmi == priority_map_.end()) {
        pmi = priority_map_.insert(std::pair<uint32_t, PriorityRing>(
                                       mci.priority, PriorityRing()))
                  .first;
    }

    pmi->second.splice(pmi->second.end(), first_data_senders_, pri);
    StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(mci.stream_id);
    sitpmi->second.ring = &(pmi->second);
}

MemCacheIter* OutputOrdering::GetIter()
{
    while (!first_data_senders_.empty()) {
        MemCacheIter& mci = first_data_senders_.front();
        if (mci.bytes_sent >= first_data_senders_threshold_) {
            SpliceToPriorityRing(first_data_senders_.begin());
        } else {
            first_data_senders_.splice(first_data_senders_.end(),
                first_data_senders_,
                first_data_senders_.begin());
            mci.max_segment_size = kInitialDataSendersThreshold;
            return &mci;
        }
    }
    while (!priority_map_.empty()) {
        PriorityRing& first_ring = priority_map_.begin()->second;
        if (first_ring.empty()) {
            priority_map_.erase(priority_map_.begin());
            continue;
        }
        MemCacheIter& mci = first_ring.front();
        first_ring.splice(first_ring.end(), first_ring, first_ring.begin());
        mci.max_segment_size = kSpdySegmentSize;
        return &mci;
    }
    return NULL;
}

void OutputOrdering::RemoveStreamId(uint32_t stream_id)
{
    StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(stream_id);
    if (sitpmi == stream_ids_.end())
        return;

    PriorityMapPointer& pmp = sitpmi->second;
    if (pmp.alarm_enabled)
        epoll_server_->UnregisterAlarm(pmp.alarm_token);
    else
        pmp.ring->erase(pmp.it);
    stream_ids_.erase(sitpmi);
}

} // namespace net
